What a month for the React ecosystem! On October 7th at the React Conference in Henderson, Nevada, the React Foundation was announced, marking a new era of technical governance for the library and its related projects, including JSX. The founding members include Amazon, Callstack, Expo, Meta, and Vercel, with Expo and Callstack representing major players in the React Native space.
Just a few days before that, the React Team released version 19.2. This release brings new features for component rendering and better performance tools.
These days, most developers start React projects using frameworks like Next.js. On the 9th of October, the team announced the beta for Next.js version 16, which will bake in support for React 19.2. With major support coming soon, let’s look at what’s new in React 19.2 and how you can use these updates in everything from side projects to production grade applications.
Preface
The changes to React 19.2 can be broken down into three core categories:
- Updates to the React core library
- Changes to React DOM, the package that enables React to update and render UI components to the web browser by interacting with the browser’s Document Object Model
- Improvements to existing features from previous changes, such as batched Suspense updates
I’m keeping these categories separate because React isn’t limited to the web. For example, Meta once maintained react-360 for VR content, though it was deprecated in 2020. Today, React can render to formats such as PDF and the Command Line Interface (CLI), among others. There’s a whole host of options that can be found in the chentsulin/awesome-react-renderer GitHub repository. As a result, updates to the core library provide benefits that extend beyond web applications.
What’s new in the Core Library?
The < Activity/> Component
In declarative, state-driven architectures like React, the UI reflects the current state at any given time. To help illustrate this, imagine a dashboard application with a collapsible sidebar menu. Users often interact with such a UI by toggling the visibility of the sidebar based on their needs. Conditional rendering lets you express how different states map to different UI structures.
For example:
const HomePage = () => {
const [isVisible, setIsVisible] = useState(false)
return (
<>
{isVisible && <Sidebar/>}
<button onClick={setIsVisible((state) => !state)}>Toggle Show Sidebar</button>
</>
)
}
When isVisible transitions from true to false, the component unmounts, and all Effects are destroyed, which cleans up any active subscriptions. No subsequent rendering or state changes can occur.
But by taking this approach, you’re missing out on a couple of features. For instance, if you wanted to temporarily hide a sidebar, but maintain its state (like the open tabs, the scroll position, the form inputs), you only have two options:
- Unmount the component → state lost, effects destroyed.
- Hide the component with CSS → state preserved, but effects (like subscriptions, event listeners, polling) continue running in the background, wasting resources.
Because React is just JavaScript, there was no built-in way to visually hide something and safely suspend its effects.
Until now. The new < Activity/> component lets you hide and later restore a component, preserving the internal state of its child components.
const HomePage = () => {
const [isVisible, setIsVisible] = useState(false);
return (
<>
<Activity mode={isVisible ? "visible" : "hidden"}>
<Sidebar />
</Activity>
<button onClick={() => setIsVisible((state) => !state)}>
Toggle Show Sidebar
</button>
</>
);
};
When the mode prop is set to hidden, the child components are hidden using the display: “none” CSS property, which removes the elements from the document and frees their original space. This is different from the visibility: hidden CSS property, which hides elements but retains their space in the layout.
While hidden, child components continue to re-render in response to new props, but at a lower priority compared to visible content.
When the boundary becomes visible again, React reveals the child components with their previous state restored and re-creates their Effects. Meaning, until we want to make the component visible again, there are no unwanted side effects.
In practice, when the < Sidebar /> component is in mode=”visible”, any navigation items that are expanded or collapsed will preserve their state. If the sidebar becomes hidden and then visible again, those items will remain in the same open or closed state they were in before.
Another way to see this is that the < Activity /> component manages background UI processes. Instead of discarding interface elements that are temporarily out of view, React shifts them into a controlled, low-priority state. The idea is closer to an operating system moving a task to the background queue; its memory and context remain intact, and it can still perform lightweight updates when needed, but it yields most of the CPU to the active, foreground tasks.
Preparing content by pre-rendering with < Activity/>
Sometimes you don’t just want to hide content, you want to prepare it. The < Activity/> component can pre-render components that will soon become visible.
This has great implications for dependency lazy loading or data pre-fetching, leading to reduced loading times.
For example, let’s assume we have a sidebar with items defined and loaded from a CMS. If we wanted to prefetch data before it becomes visible, we could render it inside an < Activity mode=”hidden”> boundary.
This allows React to start fetching data in the background using the use() hook. So by the time users open the sidebar, the data is already available and rendered, and it feels instant.
const sidebarDataPromise = fetchSidebarData()
function Sidebar() {
const data = use(sidebarDataPromise)
return (
<nav>
{data.items.map((item) => (
<a key={item.id} href={item.href}>
{item.label}
</a>
))}
</nav>
)
}
TanStack Query gotchas
The caveat to not having effects running in “hidden” mode is that any data fetching relying on running within an effect won’t be able to take advantage of the pre-rendering capabilities of the < Activity/> component. This includes, but not limited to, the useQuery hook from TanStack query, which uses a useEffect under the hood. To take advantage of this pattern with the commonly used TanStack query asynchronous state management libraries, which would also then cache the fetched data in-memory for further optimization and guarding against refetching non-stale data, you could make use of queryClient.prefetch.
const SIDEBAR_QUERY_KEY = 'sidebar';
function Sidebar() {
const queryClient = useQueryClient()
const data = use(queryClient.ensureQueryData({
queryKey: [SIDEBAR_QUERY_KEY],
queryFn: fetchSidebarData,
}))
return (
<nav>
{data.items.map((item) => (
<a key={item.id} href={item.href}>
{item.label}
</a>
))}
</nav>
)
}
An added benefit of pre-fetching with TanStack query here is that any other components subscribing to the same query key will benefit from the data having a warm cache ready to go.
The useEffectEvent Hook
If you’ve ever written a useEffect that connects to an external system, say a WebSocket, a stream, or a DOM event, you’ve probably had to battle the dependency array. The typical problem is that you want to react to something external, but the effect keeps re-running every time one of your props or state values changes. You either end up reconnecting too often or disabling the lint rule, which may leave you in the dark as the dependencies continue to grow and evolve.
Take this great example from the React Docs: you’re building a chat app, and when a user joins a new room, you want to show a notification once the connection is ready:
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => connection.disconnect();
}, [roomId, theme]);
}
This looks fine, but there’s a subtle issue. If the user switches between light and dark themes while the chat is connected, the entire effect re-runs, disconnecting and reconnecting the socket, just to show the notification with the right color. It’s probably not what you were going for. The connection should only reset when roomId changes, not because of theming. What most would do in this case is remove the theme from the dependency array. However, that results in a linter warning, and you ultimately will have to disable it with a comment.
This is where useEffectEvent shines. It lets you separate the “event reaction” logic from the “effect setup” logic, so React can handle updates to values like theme without forcing a teardown and reconnect.
Here’s the same example rewritten:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Effect runs only when roomId changes
}
The key difference is that the onConnected callback always “sees” the latest theme, but the effect itself remains stable because the event handler’s identity never changes. React treats useEffectEvent callbacks as stable by design, meaning they don’t need to appear in dependency arrays.
This pattern is incredibly useful in real apps. Think about analytics events, WebSocket subscriptions, or integrations with browser APIs. You often need to respond to events (connection open, visibility change, playback start, etc.) without tearing down your entire effect tree every time an unrelated prop changes.
So if you’ve been in the habit of sprinkling eslint-disable-next-line react-hooks/exhaustive-deps above every useEffect that listens to external events, this new addition to React’s collection of hooks finally makes that unnecessary. Just make sure to upgrade your eslint-plugin-react-hooks to latest.
Improving cache management with cacheSignal in React Server Components
The cache() function, used exclusively with React Server Components (RSCs), allows you to memoize the results of data fetching or expensive computations across requests. Starting with React 19.2, the core library introduces a new companion API, cacheSignal(), to complement the existing cache() API and provide greater control over cache lifecycles.
In short, cacheSignal() gives you an AbortSignal that matches the cache’s lifetime. When the cache expires, the signal is aborted, so any ongoing operations like fetch() calls can be cancelled smoothly.
This idea isn’t new – using abort signals is a common practice on the client side, with fetch requests that occur within effects and abort signals that allow for the correct cleanup to occur so that when a component unmounts, there aren’t any wasted resources. Now it’s built into React’s cache and rendering system.
Here’s an example:
const getUser = cache(async (id: string) => {
const signal = cacheSignal();
const response = await fetch(`/users/${id}`, { signal });
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
});
export async function UserProfile({ id }: { id: string }) {
const user = await getUser(id);
return (
<section>
<h2>{user.name}</h2>
<p>{user.email}</p>
</section>
);
}
In this example:
- getUser is wrapped in cache(), which deduplicates calls with the same arguments within React’s server cache scope.
- Inside getUser, cacheSignal() returns an AbortSignal that React will abort after rendering is conclusive. This occurs in one of three scenarios:
- React has successfully completed rendering.
- The render was aborted.
- The render has failed.
- Passing that signal to fetch() ensures that any pending network requests are immediately canceled if the render is aborted, fails, or completes.
While cacheSignal() currently only operates within the RSC environment and returns null on the client, in the official documentss, the React team has indicated plans to extend its availability to Client Components in future releases.
Performance profiling gets new powers
Chrome provides the ability to customize performance data via its extensibility API, which, with React 19.2, is finally being taken advantage of. Previously, the performance panel showed flame charts for JavaScript, layout, and paint events, but not what React was doing internally. You could see when the browser was busy, but not why.
The React DevTools Profiler, added in version 16.5, helped fill some gaps, but only from React’s point of view. It showed which components rendered, how long each render took, and what triggered them. This was useful for seeing what React did, but we were still missing info on when or how it worked with the browser. The Profiler was separate from the browser’s performance timeline, so you couldn’t match React’s scheduling with main-thread tasks or paint events.
This separation made it hard to understand concurrency and scheduling. For example, if interactions were slow, you couldn’t tell if React was blocked by the browser, yielding work, or just handling a low-priority update.

Figure 1: React Performance Tracks (Source)
React 19.2 changes this by adding React Performance Tracks to Chrome DevTools’ Performance panel. This bridges the gap between React’s scheduler and the browser’s timeline. Now, you can see React’s priorities, renders, and effects right next to standard performance data, giving you a clear view of how React works frame by frame.
The tracks are broken down into the Scheduler track and the Component track:
- Scheduler: visualizes React’s internal priorities like blocking and transition updates, showing when work starts, pauses, and completes.
- Components: shows which components are rendering or running effects.
What’s new with React DOM?
Partial Pre-rendering
Partial pre-rendering first came as an experimental feature in Next.js 14. And now, with React 19.2, it’s shipping as part of the react-dom package, bringing a new rendering model to React that allows you to combine the benefits of static and dynamic rendering.
This provides a new level of flexibility, combining the performance benefits of Static Site Generation (SSG), where an entire route is rendered to static HTML, with the freshness of Server-Side Rendering (SSR), which re-renders the page on each request.
In a nutshell, with Partial Pre-rendering:
- React pre-renders as much of the page as possible ahead of time (the static shell).
- The parts that depend on live data or user-specific information are left as “holes” (Suspense boundaries).
- When a request arrives, React resumes rendering the postponed (dynamic) parts on the server from the saved state, then streams the completed output to the browser.
This can be great for use cases such as E-commerce product pages, where product details like the title, description, and images rarely change, whereas pricing, localization, and stock generally do. With partial pre-rendering, you can serve a cached static shell instantly from a CDN to ensure the initial UI renders quickly and is close to the end user. Then, you can resume rendering only the dynamic components, such as price and stock, when the request hits the server.
Wrapping up
Beyond the core library and DOM package updates, the React team sprinkled in a few updates around batching suspense boundaries, web stream support for Node, eslint-plugin-react-hooks, and more!
As of October 20, 2025, 66.8% of websites using React are still on the 2017 release, version 16, and 10.9% on version 18, according to W3Techs. There’s still some time before these features will hit scale on the majority of production-grade applications. But that doesn’t mean it’s not important to get familiarized with what’s possible and to learn the concepts early. Isn’t that the perfect excuse to play around with them in a side project?
🔍 Frequently Asked Questions (FAQ)
1. What is new in React 19.2?
React 19.2 introduces key improvements to component rendering, performance profiling, and developer experience. Major updates include the <Activity /> component, useEffectEvent hook, cacheSignal() for RSCs, and partial pre-rendering support in React DOM.
2. How does the <Activity /> component work in React 19.2?
The new <Activity /> component enables developers to hide and later restore UI components while preserving their internal state and effects. It uses display: "none" for layout exclusion and allows low-priority updates in the background, optimizing performance without resource waste.
3. What are the benefits of pre-rendering with <Activity />?
<Activity mode="hidden"> allows React to prepare hidden components in advance, improving user experience through faster display of content. This is particularly useful for lazy-loading data or UI components, leveraging features like use() or queryClient.prefetch with TanStack Query.
4. What problem does useEffectEvent solve in React?
useEffectEvent decouples event logic from effect setup, preventing unnecessary re-renders caused by changing props or state. It offers a stable reference for event callbacks, improving code stability and reducing the need to disable lint rules.
5. How does cacheSignal() improve cache control in React Server Components?
cacheSignal() returns an AbortSignal tied to the lifecycle of React’s server-side cache. When rendering concludes or fails, any fetch requests using this signal are aborted, avoiding unnecessary network usage and improving memory efficiency.
6. What is partial pre-rendering in React 19.2?
Partial pre-rendering allows React to pre-render static parts of a page and defer dynamic rendering using Suspense boundaries. This enables hybrid rendering models that combine the speed of static pages with the freshness of dynamic content.
7. How does React 19.2 improve performance profiling in Chrome DevTools?
React 19.2 introduces React Performance Tracks in Chrome DevTools’ Performance panel. These tracks provide visibility into React’s scheduler and component behavior, enabling better debugging of concurrency and rendering timing.
8. What is the relationship between React 19.2 and Next.js 16?
Next.js 16, announced in beta shortly after React 19.2, bakes in support for the latest React version. This close alignment ensures seamless integration for developers using modern React features in production-ready applications.
9. What limitations exist with TanStack Query and <Activity />?
Because TanStack Query’s useQuery relies on useEffect, it cannot run during hidden-mode pre-renders. Developers must use methods like queryClient.ensureQueryData for preloading and caching data outside the effect lifecycle.
10. Why is the React Foundation significant?
Launched alongside React 19.2, the React Foundation formalizes community governance and includes major stakeholders like Meta, Amazon, Vercel, and Expo. It strengthens React’s roadmap transparency and long-term ecosystem alignment.






6 months access to session recordings.